/************** O T T R   C O P Y R I G H T   N O T I C E **************
 **                                                                   **
 ** © 2013 OTTR, Inc.  All rights reserved.                           **
 **                                                                   ** 
 *************** O T T R   C O P Y R I G H T   N O T I C E *************/

package com.jonathan.javatar;

import java.io.File;
import java.util.Arrays;

/**
 *
 * @author Jonathan Paz <jonathan.paz@ottr.com>
 */
public class TarEntry implements Cloneable {
    public static enum Format {V7TAR, USTAR, GNUTAR, STAR, PAX};
    static enum BlockFormat {V7TAR, USTAR, GNUTAR, GNUTAR_INCOMPLETE, STAR, PAX, PAX_INCOMPLETE, PAX_GLOBAL};

    private TarEntry parentEntry;
    private TarHeader header;

    private String name;
    private int mode;
    private int uid;
    private int gid;
    private long size;
    private long mtime;
    private int chksum;
    private char typeflag;
    private String linkname;
    private String magic;
    private String version;
    private String uname;
    private String gname;
    private int devmajor;
    private int devminor;
    private long atime;
    private long ctime;
    private Format entryFormat;

    public TarEntry(String name) {
        this.name = name.replace(File.separatorChar, '/');
        this.entryFormat = Format.PAX;
    }

    public TarEntry(TarEntry entry) {
        this.name = entry.name.replace(File.separatorChar, '/');
        this.mode = entry.mode;
        this.uid = entry.uid;
        this.gid = entry.gid;
        this.size = entry.size;
        this.mtime = entry.mtime;
        this.chksum = entry.chksum;
        this.typeflag = entry.typeflag;
        this.linkname = entry.linkname.replace(File.separatorChar, '/');
        this.magic = entry.magic;
        this.version = entry.version;
        this.uname = entry.uname;
        this.gname = entry.gname;
        this.devmajor = entry.devmajor;
        this.devminor = entry.devminor;
        this.atime = entry.atime;
        this.ctime = entry.ctime;
        this.entryFormat = entry.entryFormat;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name.replace(File.separatorChar, '/');
    }

    public int getMode() {
        return mode;
    }

    public void setMode(int mode) {
        this.mode = mode;
    }

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public int getGid() {
        return gid;
    }

    public void setGid(int gid) {
        this.gid = gid;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }

    public long getMtime() {
        return mtime;
    }

    public void setMtime(long mtime) {
        this.mtime = mtime;
    }

    public int getChksum() {
        return chksum;
    }

    public void setChksum(int chksum) {
        this.chksum = chksum;
    }

    public char getTypeflag() {
        return typeflag;
    }

    public void setTypeflag(char typeflag) {
        this.typeflag = typeflag;
    }

    public String getLinkname() {
        return linkname;
    }

    public void setLinkname(String linkname) {
        this.linkname = linkname.replace(File.separatorChar, '/');
    }

    public String getMagic() {
        return magic;
    }

    public void setMagic(String magic) {
        this.magic = magic;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }

    public String getGname() {
        return gname;
    }

    public void setGname(String gname) {
        this.gname = gname;
    }

    public int getDevmajor() {
        return devmajor;
    }

    public void setDevmajor(int devmajor) {
        this.devmajor = devmajor;
    }

    public int getDevminor() {
        return devminor;
    }

    public void setDevminor(int devminor) {
        this.devminor = devminor;
    }

    public long getAtime() {
        return atime;
    }

    public void setAtime(long atime) {
        this.atime = atime;
    }

    public long getCtime() {
        return ctime;
    }

    public void setCtime(long ctime) {
        this.ctime = ctime;
    }

    public Format getEntryFormat() {
        return entryFormat;
    }

    public void setEntryFormat(Format entryFormat) {
        this.entryFormat = entryFormat;
    }
    
    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            InternalError error = new InternalError();
            error.initCause(e);
            throw error;
        }
    }

    private static final byte[] GNUTAR_MAGIC = {'u','s','t','a','r',' ',' ', 0 };
    private static final byte[] V7TAR_MAGIC =  { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 };
    private static final byte[] USTAR_MAGIC =  {'u','s','t','a','r', 0 ,'0','0'};

    BlockFormat headerFormat(byte[] block) {
        byte[] testmagic = new byte[8];
        System.arraycopy(block, USTarHeader.MAGIC_OFFSET, testmagic, 0, 8);
        if (Arrays.equals(testmagic, V7TAR_MAGIC)) {
            return BlockFormat.V7TAR;
        } else if (Arrays.equals(testmagic, GNUTAR_MAGIC)) {
            switch (block[GNUTarHeader.TYPEFLAG_OFFSET]) {
                case 'K':
                case 'L':
                    if (block.length == 512) {
                        return BlockFormat.GNUTAR_INCOMPLETE;
                    } else {
                        int datasize = GNUTarHeader.processIntField(block, GNUTarHeader.SIZE_OFFSET, GNUTarHeader.SIZE_LENGTH);
                        datasize = (datasize + 511) / 512;
                        if (block.length >= 1024 + datasize) {
                            byte[] newblock = new byte[block.length - 512 - datasize];
                            System.arraycopy(block, 512 + datasize + 1, newblock, 0, block.length - 512 - datasize);
                            return headerFormat(newblock);
                        } else {
                            return BlockFormat.GNUTAR_INCOMPLETE;
                        }
                    }
                default:
                    return BlockFormat.GNUTAR;
            }
        } else if (Arrays.equals(testmagic, USTAR_MAGIC)) {
            switch (block[USTarHeader.TYPEFLAG_OFFSET]) {
                case 'g':
                    return BlockFormat.PAX_GLOBAL;
                case 'x':
                    if (block.length == 512) {
                        return BlockFormat.PAX_INCOMPLETE;
                    } else {
                        int datasize = USTarHeader.processIntField(block, USTarHeader.SIZE_OFFSET, USTarHeader.SIZE_LENGTH);
                        datasize = (datasize + 511) / 512;
                        if (block.length >= 1024 + datasize) {
                            byte[] newblock = new byte[block.length - 512 - datasize];
                            System.arraycopy(block, 512 + datasize + 1, newblock, 0, block.length - 512 - datasize);
                            return headerFormat(newblock);
                        } else {
                            return BlockFormat.PAX_INCOMPLETE;
                        }
                    }
                default:
                    return BlockFormat.PAX;
            }
        }
        return null;
    }

    TarHeader getHeader() {
        return header;
    }

    void setHeader(TarHeader header) {
        this.header = header;
    }

    TarEntry getParentEntry() {
        return parentEntry;
    }

    void setParentEntry(TarEntry parentEntry) {
        this.parentEntry = parentEntry;
    }
    
}
